/*
	Authors:  Chuck Tassoni, in part based on some examples by  Christian Heilmann
	at http://onlinetools.org/articles/unobtrusivejavascript.
	
	The function that gives you the most bang for your buck is  fullErrorDisplay, which
	is a quick way to use the ErrorBox and ErrorMarker classes.  See those classes
	if you need to configure them or if you only want one of the functionalities--
	showing a box of validation errors, or marking individual fields with an error css class. 
	
	This file also has methods for adding, removing, and swapping css classes that 
	may prove useful on their own.
	
	TODO: Handle checkboxes and radio buttons!

*/

//************Free floating functions*************************************
/*
	Shortcut for using ErrorBox and ErrorMarker javascript classes.
	If you can live with their default values, you can call this method like so:
	fullErrorDisplay(document.forms[0], {"surname" : "is required", "name":"is required"})
	Otherwise, try
	fullErrorDisplay(document.forms[0], {"surname" : "is required", "name":"is required"},
	anErrorBoxConfiguredAsYouLike, anErrorMarkerConfiguredAsYouLike)
*/                                                                                       
function fullErrorDisplay(form, errorsMap, errorBox, errorMarker){
	var arr = analyzeErrorsMap(form, errorsMap)
	var elementsWithError = arr[0]
	var errorMessages = arr[1]
	
	var box = errorBox || new ErrorBox()
	box.displayErrorList(form, elementsWithError, errorMessages)
	
	var idsArr = []
	for(var i = 0; i < elementsWithError.length; i++){
		idsArr.push(elementsWithError[i].id)
	}
	
	
	var m = errorMarker || new ErrorMarker()
	m.mark(form, idsArr)
}

/*
   Convenience for calling fullErrorDisplay with jsonData
   Only the first two arguments are required
 */
function showJsonErrors(form, data, errorBox, errorMarker, errorFieldName){
   var name =  errorFieldName || 'errors'
   var errorsMap = data[name] || null
   fullErrorDisplay(form, errorsMap, errorBox, errorMarker)
}

/*
	Returned elementsWithError and errorMesages arrays will be in the order in which they 
	appear in the form.
	Fields must have labels that have been associated with the ids
	of the fields.  (i.e. must use 'label for="theAssocaitedFieldId"').
	Example of errorsMap : {"surname" : "is required", "name":"is required"}
	Of course, there is no map in javascript, so this is just a javascript object
	where we treat the methodName as key and the values as error messages.
	We expect that the errorsMap will typically be created by json passed in from
	the server side.
*/
function analyzeErrorsMap(form, errorsMap){
	var errorMessages = []
	var elementsWithError = []
    if(errorsMap != null){
        for(var i = 0; i < form.elements.length; i++){
            var element = form.elements[i]
            if(typeof element.id == 'undefined' || element.id == "")
                continue;
            var evalString = "var errorMessage = errorsMap." + element.id
            eval(evalString)
            if(typeof errorMessage != 'undefined'){
                errorMessages.push(errorMessage)
                elementsWithError.push(element) //For now only allowing one error message per property
            }
        }
    }
    return [elementsWithError, errorMessages]
}

/*
 * This function courtesy of http://onlinetools.org/articles/unobtrusivejavascript/cssjsseparation.html
 * written by Christian Heilmann (http://icant.co.uk)
 * eases the dynamic application of CSS classes via DOM
 * parameters: action a, object o and class names c1 and c2 (c2 optional)
 * actions: swap exchanges c1 and c2 in object o
 *			add adds class c1 to object o
 *			remove removes class c1 from object o
 *			check tests if class c1 is applied to object o
 * example:	cssjs('swap',document.getElementById('foo'),'bar','baz');
 */

function cssjs(a,o,c1,c2)
{
	switch (a){
		case 'swap':
			o.className=!cssjs('check',o,c1)?o.className.replace(c2,c1):o.className.replace(c1,c2);
		break;
		case 'add':
			if(!cssjs('check',o,c1)){o.className+=o.className?' '+c1:c1;}
		break;
		case 'remove':
			var rep=o.className.match(' '+c1)?' '+c1:c1;
			o.className=o.className.replace(rep,'');
		break;
		case 'check':
			return new RegExp('\\b'+c1+'\\b').test(o.className)
		break;
	}
}


function addClass(obj, cls){
	cssjs('add', obj, cls)
}
function swapClass(obj, clsToRemove, clsToAdd){
	cssjs('swap', obj, clsToRemove, clsToAdd)
}
function checkClass(obj, cls){
	return cssjs('check', obj, cls)
}
function removeClass(obj, cls){
	cssjs("remove", obj, cls)
}


function removeClassFromArr(arr, cls){
	for(var i = 0; i < arr.length; i++){
		removeClass(arr[i], cls)
	}
}

function addClassToArr(arr, cls){
	for(var i = 0; i < arr.length; i++){
		addClass(arr[i], cls)
	}
}


function objectHasProperties(obj){
    for (property in obj){
        return true
    }
    return false
}

//************ErrorMarker class*************************************
function ErrorMarker(cls){
	this.cls = cls || 'error'
}
ErrorMarker.prototype.mark = function(form, idsArr){
	removeClassFromArr(form.elements, this.cls)
	for(var i = 0; i < idsArr.length; i++){
		addClass(document.getElementById(idsArr[i]), this.cls)
	}
}

//************ErrorBox class*************************************
/*
    Create this object, call displayErrorList with the correct parameters, and you'll
	see a list of errors inside a specified element, with each member of the list 
	inking to the field that's in error.  
	
	The errors appear in the order in which elements appear in the form.  The names appearing
	in the list are the labels you've associated with the form element ids.  
	
	But what if you don't want to display labels on a given page?  Simply include the
	labels, but hide them.  For example:
	<style type="text/css">
		.hidden
		{
			display:none;
		}
    </style>
	<label class="hidden" for="theId">theLabel</label>
	
	By default, errorBoxId  is 'errorBoxId', errorBoxClass is 'errorbox',
	idOfElementHoldingErrorBox (refered to as 'a specified element' above) 
	is 'errorBoxHolder', and errorBoxHeading is 'The following fields have errors:'
	
	If you don't like any of those values, set your values in prior to calling
	displayErrorList
	*/
function ErrorBox(){
	this.errorBoxId = 'errorBoxId';
	this.errorBoxClass = 'errorbox'
	this.idOfElementHoldingErrorBox = 'errorBoxHolder'
	this.errorBoxHeading =  'The following fields have errors:'
}



/*	
	Shows a list of errors inside an element, with each element of the list 
	inking to the field that's in error.  If errorMessages array has 0 length,
	the pre-existing element, if any, is removed, and no new element  is created.

*/
ErrorBox.prototype.displayErrorList = function(form, elementsWithError, errorMessages){
    
	this.removeErrorBox(this.errorBoxId)

	if(errorMessages.length == 0)
		return 

	this.makeErrorBox()
	for(var i = 0; i < errorMessages.length; i++){
		this.addErrorToList(form, elementsWithError[i], errorMessages[i])
	}

}

//Want to make private
ErrorBox.prototype.removeErrorBox = function(){
	if(document.getElementById(this.errorBoxId)){
			var em=document.getElementById(this.errorBoxId);
			em.parentNode.removeChild(em);
	}
}

//Want to make private
//Much of this taken from http://onlinetools.org/articles/unobtrusivejavascript, by
//Christian Heilmann.  
ErrorBox.prototype.makeErrorBox = function (){
	var em=document.createElement('div');
	em.id=this.errorBoxId

	addClass(em, this.errorBoxClass)

	var newp=document.createElement('p');

	newp.appendChild(document.createTextNode(this.errorBoxHeading))
	em.appendChild(newp);

	var newul=document.createElement('ul');
	em.appendChild(newul);


	document.getElementById(this.idOfElementHoldingErrorBox).appendChild(em)
}

//Want to make private
//Much of this taken from http://onlinetools.org/articles/unobtrusivejavascript, by
//Christian Heilmann.  
ErrorBox.prototype.addErrorToList = function(form, field, errorMessage){
	var em=document.getElementById(this.errorBoxId).getElementsByTagName('ul')[0];
	var newli=document.createElement('li');
	var newa=document.createElement('a');
	//Find the label corresponding to the field.
	for(var i=0;i<form.getElementsByTagName('label').length;i++)
	{
		if(form.getElementsByTagName('label')[i].htmlFor==field.id)
		{
			var txt=form.getElementsByTagName('label')[i].firstChild.nodeValue;
			break;
		}
	}

	newa.appendChild(document.createTextNode(txt));
	newa.href='#'+field.id;
	newa.onclick=function()
	{
		var loc=this.href.match(/#(\w.+)/)[1];
		document.getElementById(loc).focus();
		return false;
	}
	newli.appendChild(newa);

	var errorTextNode = document.createTextNode(":  " + errorMessage)
	newli.appendChild(errorTextNode)

	em.appendChild(newli);
}




